WPFで業務アプリを作るなら、SQLite × DataGrid × MVVM はほぼ定番構成です。 この記事では、SQLiteのデータをMVVMパターンでDataGridにバインドし、 一覧表示・編集・保存・削除までをコードビハインド最小で実装する方法をまとめます。
この記事でわかること
・SQLiteのデータをMVVMでDataGridに表示する方法
・ObservableCollection+INotifyPropertyChangedの構成
・保存・削除をコマンドで実装するパターン
・新規追加行のINSERT処理
・業務アプリ向けベストプラクティス
・SQLiteのデータをMVVMでDataGridに表示する方法
・ObservableCollection+INotifyPropertyChangedの構成
・保存・削除をコマンドで実装するパターン
・新規追加行のINSERT処理
・業務アプリ向けベストプラクティス
1. 前提:テーブルとライブラリ
- DB:SQLite(sample.db)
- テーブル:Users(Id, Name, Age)
- ライブラリ:Microsoft.Data.Sqlite
CREATE TABLE Users (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
Age INTEGER
);
2. モデル+INotifyPropertyChanged
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class User : INotifyPropertyChanged
{
private int _id;
private string _name;
private int? _age;
public int Id
{
get => _id;
set { _id = value; OnPropertyChanged(); }
}
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public int? Age
{
get => _age;
set { _age = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
3. RelayCommand(汎用コマンド)
using System;
using System.Windows.Input;
public class RelayCommand : ICommand
{
private readonly Action<object?> _execute;
private readonly Func<object?, bool> _canExecute;
public RelayCommand(Action<object?> execute, Func<object?, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute ?? (_ => true);
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public bool CanExecute(object parameter) => _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
4. ViewModel:一覧・保存・削除・再読込
using Microsoft.Data.Sqlite;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Windows.Input;
public class UsersViewModel : INotifyPropertyChanged
{
private readonly string _cs = "Data Source=sample.db";
public ObservableCollection<User> Users { get; } = new();
private User _selectedUser;
public User SelectedUser
{
get => _selectedUser;
set { _selectedUser = value; OnPropertyChanged(); }
}
public ICommand ReloadCommand { get; }
public ICommand SaveCommand { get; }
public ICommand DeleteCommand { get; }
public ICommand AddNewCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
public UsersViewModel()
{
ReloadCommand = new RelayCommand(_ => Load());
SaveCommand = new RelayCommand(_ => SaveAll(), _ => Users.Any());
DeleteCommand = new RelayCommand(u => Delete(u as User), u => u is User);
AddNewCommand = new RelayCommand(_ => AddNew());
Load();
}
public void Load()
{
Users.Clear();
using var con = new SqliteConnection(_cs);
con.Open();
using var cmd = new SqliteCommand("SELECT Id, Name, Age FROM Users ORDER BY Id", con);
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
Users.Add(new User
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Age = reader.IsDBNull(2) ? (int?)null : reader.GetInt32(2)
});
}
}
private void AddNew()
{
var u = new User { Id = 0, Name = "新規ユーザー", Age = null };
Users.Add(u);
SelectedUser = u;
}
public void SaveAll()
{
using var con = new SqliteConnection(_cs);
con.Open();
using var tran = con.BeginTransaction();
try
{
foreach (var u in Users)
{
if (u.Id == 0)
{
var insert = "INSERT INTO Users (Name, Age) VALUES (@name, @age); " +
"SELECT last_insert_rowid();";
using var cmd = new SqliteCommand(insert, con, tran);
cmd.Parameters.AddWithValue("@name", u.Name);
cmd.Parameters.AddWithValue("@age", (object?)u.Age ?? DBNull.Value);
var newId = (long)cmd.ExecuteScalar();
u.Id = (int)newId;
}
else
{
var update = "UPDATE Users SET Name = @name, Age = @age WHERE Id = @id";
using var cmd = new SqliteCommand(update, con, tran);
cmd.Parameters.AddWithValue("@name", u.Name);
cmd.Parameters.AddWithValue("@age", (object?)u.Age ?? DBNull.Value);
cmd.Parameters.AddWithValue("@id", u.Id);
cmd.ExecuteNonQuery();
}
}
tran.Commit();
}
catch
{
tran.Rollback();
throw;
}
}
private void Delete(User user)
{
if (user == null) return;
using var con = new SqliteConnection(_cs);
con.Open();
if (user.Id != 0)
{
var sql = "DELETE FROM Users WHERE Id = @id";
using var cmd = new SqliteCommand(sql, con);
cmd.Parameters.AddWithValue("@id", user.Id);
cmd.ExecuteNonQuery();
}
Users.Remove(user);
}
}
5. View(XAML):DataGrid+ボタンをすべてバインド
<Window x:Class="SampleApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleApp"
Title="SQLite × WPF DataGrid MVVM" Height="450" Width="800">
<Window.DataContext>
<local:UsersViewModel />
</Window.DataContext>
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="10" HorizontalAlignment="Right">
<Button Content="再読込" Margin="0,0,8,0"
Command="{Binding ReloadCommand}" />
<Button Content="新規追加" Margin="0,0,8,0"
Command="{Binding AddNewCommand}" />
<Button Content="保存" Margin="0,0,8,0"
Command="{Binding SaveCommand}" />
</StackPanel>
<DataGrid ItemsSource="{Binding Users}"
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
AutoGenerateColumns="False"
Margin="10"
IsReadOnly="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Id"
Binding="{Binding Id}"
IsReadOnly="True" Width="60" />
<DataGridTextColumn Header="名前"
Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
Width="*" />
<DataGridTextColumn Header="年齢"
Binding="{Binding Age, UpdateSourceTrigger=PropertyChanged}"
Width="80" />
<DataGridTemplateColumn Header="削除" Width="70">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="削除"
Padding="4,2"
Command="{Binding DataContext.DeleteCommand,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Window>
コードビハインドは InitializeComponent() だけでOKです。
6. 業務アプリでのベストプラクティス
- DataGridは ObservableCollection+INotifyPropertyChanged でバインド
- 保存・削除はコマンド経由でViewModelに集約
- 新規行は Id=0 として扱い、保存時にINSERT+ID採番
- 複数行保存はトランザクションでまとめて高速化
- コードビハインドは極力UIロジックのみに限定
まとめ:SQLite × WPF DataGrid × MVVM は“長く保守できる”鉄板構成
- ViewはXAMLバインディング中心、ロジックはViewModelに集約
- SQLiteアクセスもViewModel側に閉じ込めてテストしやすく
- 一覧・編集・追加・削除まで一貫した構成で実装できる
「とりあえず動く」から一歩進んで、 「長く保守できるMVVM構成」にしたいとき、 SQLite × WPF DataGrid × MVVM は非常に相性の良い組み合わせです。 この記事の構成をベースに、プロジェクトに合わせて拡張してみてください。